export const DATA = Symbol();

let idx = 0;

let fontZoom: number | undefined;

export function getFontZoom(): number {
	if (fontZoom === undefined) {
		const div = (
			<div style={{ fontSize: '10px', height: '1px', width: '1px', opacity: 0 }} />
		) as HTMLElement;
		document.body.appendChild(div);
		fontZoom = parseFloat(window.getComputedStyle(div).fontSize) / 10;
		document.body.removeChild(div);
	}
	return fontZoom;
}

export function genId(prefix?: string): string {
	const p = prefix ? `${prefix}-` : 'id';
	return `${p}${++idx}`;
}

export function getFontSize(element: HTMLElement): number {
	const sz =
		window.getComputedStyle(element).fontSize ||
		window.getComputedStyle(document.documentElement).fontSize ||
		'14px';
	return parseFloat(sz);
}

export function em2px(element: HTMLElement, em: number): number {
	return getFontSize(element) * em;
}

export function rem2px(rem: number): number {
	return getFontSize(document.documentElement) * rem;
}

export function size2px(
	size: number | string | undefined,
	refElem: HTMLElement,
	vertical: boolean,
): number | undefined {
	if (size === undefined) {
		return undefined;
	} else if (typeof size === 'number') {
		return size;
	} else if (/\d+(\.\d+)?px$/.test(size)) {
		return parseFloat(size);
	} else if (/\d+(\.\d+)?%$/.test(size)) {
		return refElem[vertical ? 'clientHeight' : 'clientWidth'] * (parseFloat(size) / 100);
	} else if (/\d+(\.\d+)?rem$/.test(size)) {
		return rem2px(parseFloat(size));
	} else if (/\d+(\.\d+)?em$/.test(size)) {
		return em2px(refElem, parseFloat(size));
	} else if (/\d+(\.\d+)?vw$/.test(size)) {
		return window.innerWidth * (parseFloat(size) / 100);
	} else if (/\d+(\.\d+)?vh$/.test(size)) {
		return window.innerHeight * (parseFloat(size) / 100);
	} else {
		return undefined;
	}
}

const validSize = /^\d+(\.\d+)?(px|em|rem|vh|vw|%)$/;

export function getMaxHeight(elem: HTMLElement): number {
	const style = window.getComputedStyle(elem);
	const mh = validSize.test(style.maxHeight) ? parseFloat(style.maxHeight) : Infinity;
	const h = validSize.test(elem.style.height) ? parseFloat(style.maxHeight) : Infinity;
	return Math.min(mh, h);
}

export function isInSvg(node: Element): boolean {
	let p: Element | null = node;
	while (p !== null) {
		if (p.nodeName.toLowerCase() === 'svg') {
			return true;
		}
		p = p.parentElement;
	}
	return false;
}

export function isDescendant(ancestors: Node[], descendant: Node | null): boolean {
	let p: Node | null = descendant;
	while (p !== null) {
		if (ancestors.includes(p)) {
			return true;
		}
		p = p.parentElement;
	}
	return false;
}

/**
 * Get element offset X related of parent element
 * @param elem child element
 * @param parent parent element
 * @returns the offset X
 */
export function getOffsetX(elem: HTMLElement, parent: HTMLElement): number {
	function getOffsetLeft(el: HTMLElement) {
		let x = el.offsetLeft;
		while (el.offsetParent) {
			el = el.offsetParent as HTMLElement;
			x += el.offsetLeft;
		}
		return x;
	}
	return getOffsetLeft(elem) - getOffsetLeft(parent);
}

/**
 * Get element Y offset related of parent element
 * @param elem child element
 * @param parent parent element
 * @returns the Y offset
 */
export function getOffsetY(elem: HTMLElement, parent: HTMLElement): number {
	function getOffsetTop(el: HTMLElement) {
		let y = el.offsetTop;
		while (el.offsetParent) {
			el = el.offsetParent as HTMLElement;
			y += el.offsetTop;
		}
		return y;
	}
	return getOffsetTop(elem) - getOffsetTop(parent);
}

export function hex2bin(str: string): Uint8Array {
	const arr = new Uint8Array(Math.floor(str.length / 2));
	for (let i = 0; i + 1 < str.length; i += 2) {
		const v = parseInt(str.substring(i, i + 2), 16);
		arr[i / 2] = v;
	}
	return arr;
}

export function hex2blob(hex: string, mime: string): Blob {
	return new Blob([hex2bin(hex).buffer as ArrayBuffer], { type: mime });
}

export async function delay(fn: () => void, timeout: number): Promise<void> {
	return new Promise((resolve) => {
		setTimeout(() => {
			fn();
			resolve();
		}, timeout);
	});
}

export * from '../theme/tools';
